<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body></body>
  <script>
    // 原始数据
    const data = { ok: true, text: "hello world" };
    // 存储副作用函数 重新设计的桶
    const bucket = new WeakMap();
    // 存储副作用函数的变量
    let activeEffect;

    // 清除依赖函数
    function cleanup(effectFn) {
      // 遍历 effectFn.deps 数组
      for (let i = 0; i < effectFn.deps.length; i++) {
        // deps 是依赖集合
        const deps = effectFn.deps[i];
        // 将 effectFn 从依赖集合中移除
        deps.delete(effectFn);
      }
      // 每次执行副作用函数都要清除一遍，最后需要重置 effectFn.dep 数组
      effectFn.deps.length = 0;
      console.log("effectFn.deps: ", effectFn.deps);
    }

    // 注册副作用函数的函数
    const effect = (fn) => {
      const effectFn = () => {
        // 调用 cleanup 函数完成清除工作
        cleanup(effectFn);
        // 当 effectFn 执行时，将其设置为当前激活的副作用函数
        activeEffect = effectFn;
        fn();
      };
      // activeEffect.deps 用来存储所有与该副作用函数相关联的依赖集合
      effectFn.deps = [];
      // 执行副作用函数
      effectFn();
    };

    // track 获取
    const track = (target, key) => {
      // 没有副作用函数直接返回
      if (!activeEffect) return;
      // 获取桶以targe作为键名，以set作为值
      let desMap = bucket.get(target);
      // 如果这个桶中之前没有收集这个对象的依赖那么就新建一个  Map
      if (!desMap) {
        bucket.set(target, (desMap = new Map()));
      }
      // 获取桶中对应对象的 desMap 中的 以键名作为key,以副作用函数集合作为值的数据
      let deps = desMap.get(key);
      // 如果该key为空，那么重新设置set集合
      if (!deps) {
        desMap.set(key, (deps = new Set()));
      }
      // 把当前激活的副作用函数添加到依赖集合 deps 中
      deps.add(activeEffect);
      // deps 就是一个与当前副作用函数存在联系的依赖集合
      // 将其添加到 activeEffect.deps 数组中
      activeEffect.deps.push(deps); // 新增
    };

    // trigger 设置
    const trigger = (target, key) => {
      // 根据响应对象获取 依赖集合
      const desMap = bucket.get(target);
      if (!desMap) return true;
      // 根据key获取对应属性的依赖集合
      const effects = desMap.get(key);
      // 避免无限循环
      const effectsToRun = new Set(effects);
      effectsToRun.forEach((effectFn) => effectFn());
    };

    const obj = new Proxy(data, {
      get(target, key) {
        track(target, key);
        return target[key];
      },
      set(target, key, newVal) {
        target[key] = newVal;
        trigger(target, key);
      },
    });

    // 注册副作用函数
    effect(() => {
      console.log("注册副作用函数");
      document.body.innerHTML = obj.ok ? obj.text : "not";
    });

    setTimeout(() => {
      obj.ok = false;
    }, 1000);
    setTimeout(() => {
      obj.text = "test1";
    }, 2000);
    setTimeout(() => {
      obj.ok = true;
    }, 3000);
    setTimeout(() => {
      obj.text = "test2";
    }, 4000);
  </script>
</html>
